#include "config.h"

#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <dirent.h>
#include <unistd.h>

#define DBG_SUBSYS S_LIBCONTROL

#include "limits.h"
#include "adt.h"
#include "sysy_lib.h"
#include "bmap.h"
#include "lich_md.h"
#include "net_table.h"
#include "configure.h"
#include "../replica/replica.h"
#include "table_proto.h"
#include "pool_proto.h"
#include "pool_rollback.h"
#include "md_proto.h"
#include "net_global.h"
#include "job_dock.h"
#include "ylog.h"
#include "dbg.h"

typedef pool_proto_entry_t entry_t;

static int __pool_rollback_set_log(pool_proto_t *pool_proto, const char *file, const char *snap)
{
        int ret;
        char log[MAX_BUF_LEN], tmp[MAX_NAME_LEN];

        snprintf(tmp, MAX_NAME_LEN, "%s@%s", file, snap);
        base64_encode(tmp, strlen(tmp) + 1, log);

        ret = pool_proto->xattr_set(pool_proto, LICH_SYSTEM_ATTR_ROLLBACK,
                                   log, strlen(log) + 1, O_EXCL);
        if (unlikely(ret)) {
                DINFO("pool_rollback @ "CHKID_FORMAT", log exist\n",
                      CHKID_ARG(&pool_proto->chkid));
                YASSERT(ret != EEXIST);
                GOTO(err_ret, ret);
        }

        return 0;
err_ret:
        return ret;
}

static int __pool_rollback_get_log(pool_proto_t *pool_proto, char *name)
{
        int ret, buflen = MAX_NAME_LEN, len = MAX_NAME_LEN;
        char buf[MAX_BUF_LEN];

        ret = pool_proto->xattr_get(pool_proto, LICH_SYSTEM_ATTR_ROLLBACK, buf, &buflen);
        if (unlikely(ret)) {
                GOTO(err_ret, ret);
        }

        base64_decode(buf, &len, name);

        return 0;
err_ret:
        return ret;
}

static int __pool_rollback_remove_log(pool_proto_t *pool_proto)
{
        return pool_proto->xattr_remove(pool_proto, LICH_SYSTEM_ATTR_ROLLBACK);
}

static int __rollback_setparent(const nid_t *nid, const chkid_t *chkid, const chkid_t *parent)
{
        int ret;

        if (net_islocal(nid)) {
                ret = replica_srv_setparent(chkid, parent);
                if (unlikely(ret))
                        GOTO(err_ret, ret);
        } else {
                ret = replica_rpc_setparent(nid, chkid, parent);
                if (unlikely(ret))
                        GOTO(err_ret, ret);
        }

        return 0;
err_ret:
        return ret;
}

static int __pool_rollback_stage1(pool_proto_t *pool_proto, const char *file, const char *snap,
                                 chkinfo_t *info1, chkinfo_t *info2, int redo)
{
        int ret; 
        char name[MAX_NAME_LEN];
       
        snprintf(name, MAX_NAME_LEN, ".%s.rollback", file);
        ret = pool_proto->rename(pool_proto, &pool_proto->chkid, file, &pool_proto->chkid, name);
        if (unlikely(ret)) {
                if (ret == ENOENT) {
                        YASSERT(redo);
                } else
                        GOTO(err_ret, ret);
        }

        return 0;
err_ret:
        return ret;
}

static int __pool_rollback_stage2(pool_proto_t *pool_proto, const char *file, const char *snap, int redo)
{
        int ret;
        char name[MAX_NAME_LEN];

        snprintf(name, MAX_NAME_LEN, ".%s@%s.swap", file, snap);
        ret = pool_proto->mkvolwith(pool_proto, &pool_proto->chkid, file, chkinfo);
        if (unlikely(ret)) {
                if (ret == EEXIST) {
                        YASSERT(redo);
                        DWARN("%s exist\n", name);
                } else
                        GOTO(err_ret, ret);
        }

        return 0;
err_ret:
        return ret;
}

static int __pool_rollback(pool_proto_t *pool_proto, const char *file, const char *snap, int redo)
{
        int ret;
        fileid_t fileid;
        chkinfo_t *chkinfo;
        char _chkinfo[CHKINFO_MAX];

        chkinfo = (void *)_chkinfo;
        ret = __pool_rollback_stage1(pool_proto, file, snap, redo);
        if (unlikely(ret))
                GOTO(err_ret, ret);

#if 0
        ret = __rollback_setparent(&chkinfo->diskid[0].id, &chkinfo->id, &fileid);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = pool_proto_del(pool_proto, ent->name);
        if (unlikely(ret))
                GOTO(err_ret, ret);
#endif

        return 0;
err_ret:
        return ret;
}

static int __pool_rollback_check(pool_proto_t *pool_proto, const char *file, const char *snap)
{
        int ret;
        entry_t *ent;

        UNIMPLEMENTED(__WARN__);//need more check
        (void) snap;

        ent = hash_table_find(pool_proto->name_tab, (void *)file);
        if (ent == NULL) {
                ret = ENOENT;
                GOTO(err_ret, ret);
        }

        return 0;
err_ret:
        return ret;
}

int pool_rollback(pool_proto_t *pool_proto, const char *file, const char *snap)
{
        int ret;

        ret = __pool_rollback_check(pool_proto, file, snap);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = __pool_rollback_set_log(pool_proto, file, snap);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = __pool_rollback(pool_proto, file, snap, 0);
        if (unlikely(ret))
                GOTO(err_reload, ret);

        ret = __pool_rollback_remove_log(pool_proto);
        if (unlikely(ret))
                GOTO(err_reload, ret);

        return 0;
err_reload:
        pool_proto->ltime = 0;
err_ret:
        return ret;
}

int pool_rollback_load(pool_proto_t *pool_proto)
{
        int ret;
        char name[MAX_NAME_LEN];
        entry_t *ent;

        ret = __pool_rollback_get_log(pool_proto, name);
        if (unlikely(ret)) {
                if (ret == ENOKEY)
                        goto out;
                else
                        GOTO(err_ret, ret);
        }

        ent = hash_table_find(pool_proto->name_tab, (void *)name);
        if (ent == NULL) {
                ret = ENOENT;
                GOTO(err_ret, ret);
        }

        ret = __pool_rollback(pool_proto, ent, 1);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = __pool_rollback_remove_log(pool_proto);
        if (unlikely(ret))
                GOTO(err_ret, ret);

out:
        return 0;
err_ret:
        return ret;
}
